El trabajo se basa en un fichero csv extraido del sitio web KAGGLE llamado “sleep_health_lifestyle_dataset.csv”. Este fichero contiene una serie de columnas que las usaremos para intentar predecir enfermedades o la no tenencia de una sobre el sueño. Esa variable se encuentra en la última columna.
Comienzo con la lectura de los datos desde el fichero csv:
A continuación pasaremos a una visualización del DataFrame para hacernos un poco la idea las distintas variables que tiene, que trabajo tendremos que acometer a lo largo del proyecto en cuestión de depuración de datos si hiciera falta, que representaciones gráficas pudieran resultar de interés.
# A tibble: 6 × 13
`Person ID` Gender Age Occupation `Sleep Duration (hours)`
<dbl> <chr> <dbl> <chr> <dbl>
1 1 Male 29 Manual Labor 7.4
2 2 Female 43 Retired 4.2
3 3 Male 44 Retired 6.1
4 4 Male 29 Office Worker 8.3
5 5 Male 67 Retired 9.1
6 6 Female 47 Student 6.1
# ℹ 8 more variables: `Quality of Sleep (scale: 1-10)` <dbl>,
# `Physical Activity Level (minutes/day)` <dbl>,
# `Stress Level (scale: 1-10)` <dbl>, `BMI Category` <chr>,
# `Blood Pressure (systolic/diastolic)` <chr>, `Heart Rate (bpm)` <dbl>,
# `Daily Steps` <dbl>, `Sleep Disorder` <chr>
Como podemos ver de un primer vistazo, nos encontramos con la primera columna que es un id, y por lo tanto podemos prescindir de el tanto para la visualización como para lo modelos. Posteriormente nos encontramos variables como genero y edad cuyo estudio puede ser interesante si mujeres u hombres sufren mas o menos enfermedades del sueño, o si estas se ven afectadas por la franja de edad donde se encuentre la persona. Luego una variable sobre el trabajo que desarrolla la persona que también resulta interesante para los mismos estudios que antes. Tras esto tenemos variables mas relacionadas con el sueño como el numero de horas que duerme la persona o la calidad del sueño que seguro serán muy importantes para la fase de los modelos. Continuando, nos encontramos con una variable, la presión sanguínea que vemos que en una misma variable agrupa dos informaciones, la presión baja y alta separadas por una barra, por lo que como depuración tendremos que separar esta variable en otras dos.
Person ID Gender Age Occupation
Min. : 1.0 Length:400 Min. :18.00 Length:400
1st Qu.:100.8 Class :character 1st Qu.:29.00 Class :character
Median :200.5 Mode :character Median :40.00 Mode :character
Mean :200.5 Mean :39.95
3rd Qu.:300.2 3rd Qu.:49.00
Max. :400.0 Max. :90.00
Sleep Duration (hours) Quality of Sleep (scale: 1-10)
Min. : 4.100 Min. : 1.000
1st Qu.: 5.900 1st Qu.: 4.700
Median : 8.200 Median : 6.100
Mean : 8.041 Mean : 6.126
3rd Qu.:10.125 3rd Qu.: 7.425
Max. :12.000 Max. :10.000
Physical Activity Level (minutes/day) Stress Level (scale: 1-10)
Min. : 10.00 Min. : 1.000
1st Qu.: 35.00 1st Qu.: 3.000
Median : 65.50 Median : 5.000
Mean : 64.98 Mean : 5.473
3rd Qu.: 94.00 3rd Qu.: 8.000
Max. :120.00 Max. :10.000
BMI Category Blood Pressure (systolic/diastolic) Heart Rate (bpm)
Length:400 Length:400 Min. : 50.00
Class :character Class :character 1st Qu.: 63.00
Mode :character Mode :character Median : 77.00
Mean : 75.99
3rd Qu.: 90.00
Max. :100.00
Daily Steps Sleep Disorder
Min. : 2067 Length:400
1st Qu.: 6165 Class :character
Median :11786 Mode :character
Mean :11077
3rd Qu.:15878
Max. :19958
De esta manera vemos el tipo de datos que tiene cada variable por si es necesario cambiarlo, junto con algunos datos interesantes de las variables.
Continuamos con la adecuación de los datos que nos proporciona el DataFrame para poder representarlo gráficamente y construir modelos adecuadamente.
Nuestro primer paso, como comentamos nada mas visualizar los datos es eliminar la primera variable:
Female Male
201 199
Normal Obese Overweight Underweight
91 98 109 102
Manual Labor Office Worker Retired Student
96 99 95 110
datos$`Sleep Disorder` <- datos %$%
`Sleep Disorder` %>%
as.factor()
summary(datos$`Sleep Disorder`) Insomnia None Sleep Apnea
79 290 31
A continuación pasamos a cambiar la variable Blood Pressure. Como hemos comentado al comienzo, esta variable contiene la presión alta y baja en una sola y separada por la barra “/”. Lo que haremos es dividir esta variable en otras dos que ya renombraremos separando por esa barra.
Así tendríamos dividida la variable en dos columnas, una con la presión alta (la primera) y la otra con la presión baja. Pasamos a guardar cada una como variables y a eliminar esa variable que no nos sirve, haciendo todo el proceso de continuo.
Press_High <- datos %$%
`Blood Pressure (systolic/diastolic)` %>%
str_split(.,pattern="/",simplify = T) %>%
.[,1] %>%
as.numeric()
Press_Low <- datos %$%
`Blood Pressure (systolic/diastolic)` %>%
str_split(.,pattern="/",simplify = T) %>%
.[,2] %>%
as.numeric()
datos$Press_High <- Press_High
datos$Press_Low <- Press_LowAhora solo nos queda eliminar la columna que hemos transformado.
Gender Age Occupation Sleep Duration (hours)
Female:201 Min. :18.00 Manual Labor : 96 Min. : 4.100
Male :199 1st Qu.:29.00 Office Worker: 99 1st Qu.: 5.900
Median :40.00 Retired : 95 Median : 8.200
Mean :39.95 Student :110 Mean : 8.041
3rd Qu.:49.00 3rd Qu.:10.125
Max. :90.00 Max. :12.000
Quality of Sleep (scale: 1-10) Physical Activity Level (minutes/day)
Min. : 1.000 Min. : 10.00
1st Qu.: 4.700 1st Qu.: 35.00
Median : 6.100 Median : 65.50
Mean : 6.126 Mean : 64.98
3rd Qu.: 7.425 3rd Qu.: 94.00
Max. :10.000 Max. :120.00
Stress Level (scale: 1-10) BMI Category Heart Rate (bpm) Daily Steps
Min. : 1.000 Normal : 91 Min. : 50.00 Min. : 2067
1st Qu.: 3.000 Obese : 98 1st Qu.: 63.00 1st Qu.: 6165
Median : 5.000 Overweight :109 Median : 77.00 Median :11786
Mean : 5.473 Underweight:102 Mean : 75.99 Mean :11077
3rd Qu.: 8.000 3rd Qu.: 90.00 3rd Qu.:15878
Max. :10.000 Max. :100.00 Max. :19958
Sleep Disorder Press_High Press_Low
Insomnia : 79 Min. :109.0 Min. :60.00
None :290 1st Qu.:115.0 1st Qu.:66.00
Sleep Apnea: 31 Median :122.0 Median :73.00
Mean :122.2 Mean :73.04
3rd Qu.:128.0 3rd Qu.:79.00
Max. :145.0 Max. :96.00
De esta manera Ya tenemos todas las variables de manera adecuada para la representación gráfica de estudios que nos interesen para una posterior construcción de modelos.
Antes de terminar exportamos en un fichero nuevo los datos depurados para posibles consultas. Recordamos que para exportarlos, los datos que están convertidos a factor debemos pasarlos a tipo character:
En esta primera gráfica comparamos la presión alta y la presión baja en
función del tipo de enfermedad del sueño que tenga la persona. Una vez
hecha la representación no podemos inferir gran cosa a partir del
gráfico, puesto que todas las enfermedades están mas o menos igualmente
representadas para valores similares de presión arterial
datos %>%
ggplot(aes(Press_High,Press_Low,color=`Sleep Disorder`)) +
geom_point() +
geom_smooth(aes(linetype = `Sleep Disorder`))
Como podemos observar las linea que interpola cada conjunto de puntos en
función de la clase de enfermedad del sueño que tenga es muy similar. Y
hay que recalcar que los intervalos de confianza se cortan por lo que
podemos descartar que estas variables nos sirvan para identificar que
tipo de enfermedad posee la persona
datos %>%
ggplot() +
stat_summary(aes(`Sleep Disorder`,`Heart Rate (bpm)`),
fun=median,
fun.min = min,
fun.max = max)Al igual que en el caso anterior parece que vuelven a ser muy similares las pulsaciones independientemente de la enfermedad que presente el individuo. Lo único que podríamos destacar es que los individuos con apnea del sueño la tienen algo mas alta
En esta gráfica si que parece que podemos sacar alguna que otra conclusión mas: en el caso de las personas que sufren insomnio, hay una mayor proporción de personas que están demasiado delgados; en el caso de las personas que no sufren ninguna enfermedad parece que hay mayor proporción e personas que sufren sobrepeso. Por otro lado de las personas que sufren apnea del sueño parece que tienen igual proporción.
Con esta gráfica podemos ver mas claro el diagrama anterior. Se hace notar mas que las personas con un indice de masa normal están representadas en menor proporción en el conjunto de personas sin enfermedad del sueño
datos %>%
count(Occupation,`Stress Level (scale: 1-10)`,`Sleep Disorder`) %>%
group_by(Occupation,`Sleep Disorder`) %>%
mutate(Proportion = n/sum(n)) %>%
ggplot(aes(`Stress Level (scale: 1-10)`,Proportion,fill=`Sleep Disorder`))+
geom_col(position = 'dodge') +
scale_y_continuous(labels = scales::percent_format()) +
facet_wrap(~Occupation,nrow = 2)En este gráfico comparamos por la ocupación que tienen las personas y su enfermedad (o ausencia de ella) el nivel de estrés que dicen tener por si esto pudiera tener un comportamiento específico. Por ejemplo, para las personas que trabajan en labores manuales, los que sufren apnea del sueño son los que en mayor proporción tienen altos niveles de estrés. Lo mismo ocurre para el caso de los estudiantes, que en gran proporción los que dicen sufrir niveles altos de estrés sufren apnea del sueño. En cambio, las personas que sufren insomnio, en cualquier ocupación laboral, parece que se encuentran en mayor porcentaje en niveles de estrés bajos, mientras que las personas que no sufren ninguna enfermedad del sueño parecen estar repartidos los porcentajes entre los distintos niveles de estrés para cualquier ocupación
datos %>%
ggplot(aes(`Sleep Duration (hours)`,`Quality of Sleep (scale: 1-10)`,color=`Sleep Disorder`)) +
geom_point()Volvemos a encontrarnos una situación en la que no podemos sacar demasiadas conclusiones puesto que no se observa un patrón en los datos. Lo mas reseñable es que la mayoría de puntos de personas con apnea del sueño tienen una calidad del sueño intermedio, ni valores muy altos ni muy bajos. Vamos a realizar una curva de regresión con su intervalo de confianza para asegurarnos de este patrón
datos %>%
ggplot(aes(`Sleep Duration (hours)`,`Quality of Sleep (scale: 1-10)`,color=`Sleep Disorder`)) +
geom_point() +
geom_smooth(aes(linetype = `Sleep Disorder`))Vemos que parece que estábamos en lo cierto que no podemos sacar conclusiones puesto que los tres intervalos de confianza se cortan y por tanto no podemos inferir que con una calidad del sueño mejor o pero o mas o menos horas de sueño pueda referirse a una persona con una determinada enfermedad del sueño.
A partir de este instante trabajamos con python.
Leemos y vemos una breve descripción de los datos para ver que no ha habido ningún problema:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 400 entries, 0 to 399
Data columns (total 13 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Gender 400 non-null object
1 Age 400 non-null int64
2 Occupation 400 non-null object
3 Sleep Duration (hours) 400 non-null float64
4 Quality of Sleep (scale: 1-10) 400 non-null float64
5 Physical Activity Level (minutes/day) 400 non-null int64
6 Stress Level (scale: 1-10) 400 non-null int64
7 BMI Category 400 non-null object
8 Heart Rate (bpm) 400 non-null int64
9 Daily Steps 400 non-null int64
10 Sleep Disorder 400 non-null object
11 Press_High 400 non-null int64
12 Press_Low 400 non-null int64
dtypes: float64(2), int64(7), object(4)
memory usage: 40.8+ KB
Dividimos entre nuestra variable objetivo y las que usamos de predictoras:
Particionamos los datos en entrenamiento y test para la construcción de los modelos.
En las anteriores secciones nos hemos dado cuenta que hay muchos mas pacientes sin enfermedad que con apnea o insomnio. Veamos en términos porcentuales:
Sleep Disorder
No enfermedad 0.7250
Insomnia 0.1975
Sleep Apnea 0.0775
Name: proportion, dtype: float64
Como no tenemos la misma proporción en unas clases que en otras tenemos que separarlos intentando mantener las proporciones.
Dividimos las variables predictoras en las que son numéricas y las que son categóricas:
<class 'pandas.core.frame.DataFrame'>
Index: 300 entries, 161 to 357
Data columns (total 3 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Gender 300 non-null object
1 Occupation 300 non-null object
2 BMI Category 300 non-null object
dtypes: object(3)
memory usage: 9.4+ KB
<class 'pandas.core.frame.DataFrame'>
Index: 300 entries, 161 to 357
Data columns (total 9 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Age 300 non-null int64
1 Sleep Duration (hours) 300 non-null float64
2 Quality of Sleep (scale: 1-10) 300 non-null float64
3 Physical Activity Level (minutes/day) 300 non-null int64
4 Stress Level (scale: 1-10) 300 non-null int64
5 Heart Rate (bpm) 300 non-null int64
6 Daily Steps 300 non-null int64
7 Press_High 300 non-null int64
8 Press_Low 300 non-null int64
dtypes: float64(2), int64(7)
memory usage: 23.4 KB
Ahora construimos nuestro pipeline para transformar los datos. Como en el apartado de visualización no hemos visto que haya ninguna variable que podamos decir que separa a nuestra variable objetivo hacemos un análisis en componentes prinicpales, para ver si reduciendo la dimensionalidad de los datos lo obtenemos de una mejor manera:
Ya tenemos los conjuntos de entrenamiento y test listos para crear los modelos y ver el rendimiento de los mismos
Una vez construimos el modelo pasamos a ver su efectividad comparando el resultado de pasar el conjunto test por el modelo con el verdadero valor de la variable objetivo:
GaussianNB()In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
GaussianNB()
precision recall f1-score support
Insomnia 0.00 0.00 0.00 20
No enfermedad 0.72 1.00 0.84 72
Sleep Apnea 0.00 0.00 0.00 8
accuracy 0.72 100
macro avg 0.24 0.33 0.28 100
weighted avg 0.52 0.72 0.60 100
GridSearchCV(cv=15, estimator=RandomForestClassifier(random_state=47563),
param_grid=[{'max_features': [2, 4, 6, 8],
'n_estimators': [3, 5, 7, 9]},
{'bootstrap': [True], 'max_features': [5],
'n_estimators': [5]}],
return_train_score=True, scoring='accuracy')In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. GridSearchCV(cv=15, estimator=RandomForestClassifier(random_state=47563),
param_grid=[{'max_features': [2, 4, 6, 8],
'n_estimators': [3, 5, 7, 9]},
{'bootstrap': [True], 'max_features': [5],
'n_estimators': [5]}],
return_train_score=True, scoring='accuracy')RandomForestClassifier(max_features=2, n_estimators=9, random_state=47563)
RandomForestClassifier(max_features=2, n_estimators=9, random_state=47563)
Vemos ahora cuales son los mejores valores de hiperparámetros y por tanto cuál es el mejor modelo de Random Forest:
RandomForestClassifier(max_features=2, n_estimators=9, random_state=47563)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
RandomForestClassifier(max_features=2, n_estimators=9, random_state=47563)
RandomForestClassifier(max_features=2, n_estimators=9, random_state=47563)
precision recall f1-score support
Insomnia 0.17 0.15 0.16 20
No enfermedad 0.71 0.79 0.75 72
Sleep Apnea 0.00 0.00 0.00 8
accuracy 0.60 100
macro avg 0.29 0.31 0.30 100
weighted avg 0.55 0.60 0.57 100
Vamos a pasarle al modelo otra vez una serie de hiperparámetros que sera la profundidad máxima del árbol para ver otra vez cual es la mejor profundidad de todas ellas y quedarnos con el mejor modelo:
GridSearchCV(cv=15, estimator=DecisionTreeClassifier(random_state=47563),
param_grid=[{'max_depth': [3, 5, 7, 9, 11]}],
return_train_score=True, scoring='accuracy')In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. GridSearchCV(cv=15, estimator=DecisionTreeClassifier(random_state=47563),
param_grid=[{'max_depth': [3, 5, 7, 9, 11]}],
return_train_score=True, scoring='accuracy')DecisionTreeClassifier(max_depth=3, random_state=47563)
DecisionTreeClassifier(max_depth=3, random_state=47563)
Vemos ahora cuales son los mejores valores de hiperparámetros y por tanto cuál es el mejor modelo de Decision Tree:
DecisionTreeClassifier(max_depth=3, random_state=47563)
precision recall f1-score support
Insomnia 0.00 0.00 0.00 20
No enfermedad 0.72 0.99 0.84 72
Sleep Apnea 0.00 0.00 0.00 8
accuracy 0.71 100
macro avg 0.24 0.33 0.28 100
weighted avg 0.52 0.71 0.60 100
GridSearchCV(cv=15, estimator=KNeighborsClassifier(metric='hamming'),
param_grid=[{'n_neighbors': [3, 5, 7, 9, 11]}],
return_train_score=True, scoring='accuracy')In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. GridSearchCV(cv=15, estimator=KNeighborsClassifier(metric='hamming'),
param_grid=[{'n_neighbors': [3, 5, 7, 9, 11]}],
return_train_score=True, scoring='accuracy')KNeighborsClassifier(metric='hamming', n_neighbors=9)
KNeighborsClassifier(metric='hamming', n_neighbors=9)
Vemos ahora cuales son los mejores valores de hiperparámetros y por tanto cuál es el mejor modelo de KNN:
KNeighborsClassifier(metric='hamming', n_neighbors=9)
precision recall f1-score support
Insomnia 0.20 1.00 0.33 20
No enfermedad 0.00 0.00 0.00 72
Sleep Apnea 0.00 0.00 0.00 8
accuracy 0.20 100
macro avg 0.07 0.33 0.11 100
weighted avg 0.04 0.20 0.07 100
GridSearchCV(cv=15, estimator=MLPClassifier(random_state=47563),
param_grid=[{'activation': ['relu'],
'hidden_layer_sizes': [(5, 5), (5, 10), (5, 15),
(10, 5), (10, 10), (10, 15),
(15, 5), (15, 10), (15, 15)],
'max_iter': [500, 1000], 'solver': ['adam']}],
return_train_score=True, scoring='accuracy')In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. GridSearchCV(cv=15, estimator=MLPClassifier(random_state=47563),
param_grid=[{'activation': ['relu'],
'hidden_layer_sizes': [(5, 5), (5, 10), (5, 15),
(10, 5), (10, 10), (10, 15),
(15, 5), (15, 10), (15, 15)],
'max_iter': [500, 1000], 'solver': ['adam']}],
return_train_score=True, scoring='accuracy')MLPClassifier(hidden_layer_sizes=(5, 5), max_iter=500, random_state=47563)
MLPClassifier(hidden_layer_sizes=(5, 5), max_iter=500, random_state=47563)
Vemos ahora cuales son los mejores valores de hiperparámetros y por tanto cuál es el mejor modelo de KNN:
MLPClassifier(hidden_layer_sizes=(5, 5), max_iter=500, random_state=47563)
Nos quedamos con el parámetro con menos nodos en cada capa oculta y menor número de iteraciones. Pasamos a ver las métricas del modelo
precision recall f1-score support
Insomnia 0.00 0.00 0.00 20
No enfermedad 0.72 1.00 0.84 72
Sleep Apnea 0.00 0.00 0.00 8
accuracy 0.72 100
macro avg 0.24 0.33 0.28 100
weighted avg 0.52 0.72 0.60 100